//-----------------------------------------------------------------------
// <copyright file="ToolTaskBase.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Andy Reeves</author>
// <email>andyr</email>
// <date>2004-04-13</date>
// <summary>provides a base class for all tasks that call to an external .exe</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Tools
{
    #region Using directives

    using Microsoft.Build.Tasks;
    using System;
    using System.Text;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    using System.Resources;
    using System.Reflection;
    using System.Xml;
    using System.IO;
    using System.Collections;
    using System.Globalization;
    using System.Diagnostics;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using Microsoft.Build.Shared;

    #endregion

    /// <summary>
    /// A base class for all tasks that call an external exe.
    /// </summary>
    public abstract class ToolTaskBase : ToolTask
    {
        private Hashtable bag = new Hashtable();
        private Utilities utilities;
        
        protected ToolTaskBase(): base()
        {
            this.TaskResources = Resources.GetResourceManager();
            this.utilities = new Utilities(this, TaskResources);
        }

        protected virtual void AddCommandLineCommands(CommandLineBuilderExtension commandLine)
        {
        }

        protected override string GenerateCommandLineCommands()
        {
            CommandLineBuilderExtension extension = new CommandLineBuilderExtension();
            this.AddCommandLineCommands(extension);
            return extension.ToString();
        }

        protected internal Hashtable Bag
        {
            get { return this.bag; }
        }

        /// <summary>
        /// A helper Utilities class.
        /// </summary>
        /// <value></value>
        internal Utilities Utilities
        {
            get { return this.utilities; }
        }

        /// <summary>
        /// Appends the switch to the commandLine if parameterName is true.
        /// </summary>
        /// <param name="commandLine">The commandline builder to append to.</param>
        /// <param name="switchName">The switch to add.</param>
        /// <param name="parameterName">The parameterName to check to see if its true.</param>
        internal void AppendSwitchWhenTrue(CommandLineBuilder commandLine, string switchName, string parameterName)
        {
            object obj = this.bag[parameterName];
            if (obj != null)
            {
                bool value = (bool) obj;

                if (value)
                {
                    commandLine.AppendSwitch(switchName);
                }
            }
        }

        /// <summary>
        /// Appends the switch and the parameters value if the parameterName is not String.Empty.
        /// </summary>
        /// <param name="commandLine">The commandline builder to append to.</param>
        /// <param name="switchName">The switch to add.</param>
        /// <param name="parameterName">The parameterName to extract the value from the PropertyBag.</param>
        internal void AppendSwitchAndValueWhenNotStringEmpty(CommandLineBuilder commandLine, string switchName, string parameterName)
        {
            this.AppendSwitchAndValueWhenNotStringEmpty(commandLine, switchName, parameterName, " ");
        }
        /// <summary>
        /// Appends the switch and the parameters value if the parameterName is not String.Empty.
        /// </summary>
        /// <param name="commandLine">The commandline builder to append to.</param>
        /// <param name="switchName">The switch to add.</param>
        /// <param name="parameterName">The parameterName to extract the value from the PropertyBag.</param>
        /// <param name="separator">The string to use between the switch and the value.</param>
        internal void AppendSwitchAndValueWhenNotStringEmpty(CommandLineBuilder commandLine, string switchName, string parameterName, string separator)
        {
            object obj = this.bag[parameterName];
            if (obj != null)
            {
                string value = (string) obj;

                if (value != null && value.Length > 0)
                {
                    //TODO Wht if the value contains spaces?
                    //We need to put quotes around it I think
                    commandLine.AppendSwitch(switchName + separator + value);
                }
            }
        }        


        protected internal int GetIntParameterWithDefault(string parameterName, int defaultValue)
        {
            object obj1 = this.bag[parameterName];
            if (obj1 == null)
            {
                return defaultValue;
            }
            return (int)obj1;
        }

        protected internal bool GetBoolParameterWithDefault(string parameterName, bool defaultValue)
        {
            object obj1 = this.bag[parameterName];
            if (obj1 == null)
            {
                return defaultValue;
            }
            return (bool)obj1;
        }

        /// <summary>
        /// Returns the ExitCode from the underlying tool that we called.
        /// </summary>
        /// <value>0 genenrally indicts success but some tools may not return the value correctly.</value>
        [Output]
        public new int ExitCode
        {
            get { return (int)Bag["ExitCode"]; }
            set { Bag["ExitCode"] = value; }
        }

        internal string ComputePathToTool()
        {
            string pathToTool;

            if (ToolPath != null && ToolPath.Length > 0)
            {
                // If the project author passed in a ToolPath, always use that.
                pathToTool = Path.Combine(ToolPath, ToolName);
            }
            else
            {
                // Otherwise, try to find the tool ourselves.
                pathToTool = GenerateFullPathToTool();
            }

            // only look for the file if we have a path to it. If we have just the file name, we'll 
            // look for it in the path
            if ((pathToTool != null) && (Path.GetFileName(pathToTool).Length < pathToTool.Length) && !File.Exists(pathToTool))
            {
                Log.LogErrorWithCodeFromResources("ToolTask.ToolExecutableNotFound", pathToTool);
                return null;
            }

            return pathToTool;
        }
 
 /*
 * GK Base class is now good, no longer need to overide these following methods.
 * 
 * 
        protected new void LogPathToTool(string toolName, string pathToTool)
        {
            Log.LogMessageFromResources(MessageImportance.Low, "ToolTask.ToolLocation", this.GetType().Name, toolName, pathToTool);
        }

        protected new int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
        {
            LogPathToTool(ToolName, pathToTool);

            string responseFile = null;
            Process proc = null;
            bool errorsLogged = false;
            int exitCode = 0;

            try
            {
                // if this tool supports response files
                if (responseFileCommands.Length > 0)
                {
                    // put all the parameters into a temporary response file so we don't have to worry about how long the command-
                    // line is going to be
                    try
                    {
                        responseFile = Path.GetTempFileName();
                    }
                    catch (IOException e)
                    {
                        // Need to use shared resources here, note LogShared
                        throw new IOException(Log.FormatResourceString("Shared.FailedCreatingTempFile", e.Message), e);
                    }

                    // Use the encoding specified by the overridable ResponseFileEncoding property
                    using (StreamWriter responseFileStream = new StreamWriter(responseFile, false, this.ResponseFileEncoding))
                    {
                        responseFileStream.Write(responseFileCommands);
                    }

                    responseFileCommands = GetResponseFileSwitch(responseFile);
                }

                // If the command is too long, it will most likely fail.  The command-line 
                // arguments passed into any process cannot exceed 32768 characters.  But depending
                // on the structure of the command, longer commands might work, or shorter 
                // commands might fail -- to play it safe, we warn at 32000.
                // Note: cmd.exe has a buffer limit of 8K, but we're not using cmd.exe here,
                // so we can go past 8K easily.
                if ((commandLineCommands.Length + responseFileCommands.Length) > 32000)
                {
                    Log.LogWarningWithCodeFromResources("ToolTask.CommandTooLong", this.GetType().Name);
                }

                // Build up the command line that will be spawned.
                string commandLine = commandLineCommands + " " + responseFileCommands;

                ProcessStartInfo startInfo = new ProcessStartInfo(pathToTool, commandLine);
                startInfo.CreateNoWindow = true;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardError = true;
                startInfo.RedirectStandardOutput = true;

                // Some applications such as xcopy.exe fail without error if there's no stdin stream.
                // See bug 323655.
                startInfo.RedirectStandardInput = true;

                StringDictionary envOverrides = EnvironmentOverride;
                if (null != envOverrides)
                {
                    foreach (DictionaryEntry entry in envOverrides)
                    {
                        startInfo.EnvironmentVariables.Remove((string)entry.Key);
                        startInfo.EnvironmentVariables.Add((string)entry.Key, (string)entry.Value);
                    }
                }

                // PPD@31115: TODO: Need to set startInfo.WorkingDirectory also
                // How to get it? Here are the ways I can think of:
                // 1. Each task can provide it like the ToolName property
                //    A task can get this information from project system through the host object
                // 2. We have a callback to the build Engine implemented today.
                //    IBuildEngine should provide "reflection" functionality on the current project file
                //    Meaning - a task can completely read the project file using this "reflection" functionality
                //    We can then use this functionality to read the MSBuildProjectDirectory property from the project file 

                // Start the process
                proc = Process.Start(startInfo);

                // start a thread to log messages from stderr
                ConcurrentLoggingHelper concurrentErrorLoggingHelper = new ConcurrentLoggingHelper(Log, new StreamReader(proc.StandardError.BaseStream, this.StandardErrorEncoding), MessageImportance.High);
                concurrentErrorLoggingHelper.StartLogging();

                // start a thread to log messages from stdout
                ConcurrentLoggingHelper concurrentOutputLoggingHelper = new ConcurrentLoggingHelper(Log, new StreamReader(proc.StandardOutput.BaseStream, this.StandardOutputEncoding), MessageImportance.Low);
                concurrentOutputLoggingHelper.StartLogging();

                // Wait for the process to complete or until the specified timeout
                proc.WaitForExit(this.Timeout);

                bool loggingThreadsStopped = false;

                // Did we have timout specified? Kill the process if not finished yet
                if ((this.Timeout != int.MaxValue) && !proc.HasExited)
                {
                    // abort the logging threads
                    loggingThreadsStopped = true;
                    concurrentErrorLoggingHelper.AbortLogging();
                    concurrentOutputLoggingHelper.AbortLogging();

                    Log.LogWarningFromResources("Shared.KillingProcess", this.Timeout);
                    proc.Kill();
                }

                // Hack necessitated by VSWhidbey 118690.
                // Process.WaitForExit() may return prematurely. We need to check to be sure.
                while (!proc.HasExited)
                {
                    System.Threading.Thread.Sleep(50);
                }

                if (!loggingThreadsStopped)
                {
                    // stop the logging threads
                    concurrentErrorLoggingHelper.StopLogging();
                    concurrentOutputLoggingHelper.StopLogging();
                }

                // check if any errors were logged
                errorsLogged = concurrentOutputLoggingHelper.HasLoggedErrors | concurrentErrorLoggingHelper.HasLoggedErrors;
            }
            finally
            {
                // Delete the temp file used for the response file.
                if (responseFile != null)
                {
                    File.Delete(responseFile);
                }

                // get the exit code and release the process handle
                if (proc != null)
                {
                    exitCode = proc.ExitCode;
                    proc.Close();
                    proc = null;
                }

                // If the tool exited cleanly, but logged errors then assign a failing exit code (-1)
                if (exitCode == 0 && (errorsLogged))
                {
                    exitCode = -1;
                }

                this.ExitCode = exitCode;
            }                        
            return exitCode;
        }
*/

        /// <summary>
        /// Main execute method. Handles bubbled up exceptions and will log the errors along
        /// with all the public properties of the task.
        /// </summary>
        /// <remarks>
        /// This method is sealed to prevent accidental implementations based on the previous pattern.
        /// Instead subclasses should implement the InternalExecute method.
        /// </remarks>
        /// <returns>true on success</returns>
        public override sealed bool Execute()
        {
            try
            {
                this.InternalExecute();
                return true;
            }

            catch (TaskException taskException)
            {
                try
                {
                    Log.LogError(Utilities.CreateExceptionInformation(taskException));
                }
                catch (Exception ex)
                {
                    if (ex.Message == TaskResources.GetString("PseudoEngineException"))
                    {
                        Log.LogError("");
                    }
                    else
                    {
                        Log.LogErrorFromException(ex, true);
                    }
                }
                return false;
            }
            catch (Exception e)
            {
                // Wrap the exception as an InnerException, add some 
                // information about the public properties and bubble it up             
                try
                {
                    Log.LogError(Utilities.CreateExceptionInformation(e));
                }
                catch (Exception ex)
                {
                    Log.LogErrorFromException(ex, true);
                }
                return false;
            }
        }


        // GK Sept 29 
        // Simplified InternalExecute to work with RTM of Whidbey
        // (No longer uses host object since this is not relevant to our set of tool tasks)
        /// <summary>
        /// Performs the action of this task.
        /// </summary>
        protected virtual void InternalExecute()
        {
            if (!this.ValidateParameters())
            {
                throw new TaskException("ToolTask.InvalidParameters");
            }
            try
            {
                string commandLineCmds = this.GenerateCommandLineCommands();
                string commandResponseFile = this.GenerateResponseFileCommands();

                if ((commandLineCmds == null) || (commandLineCmds.Length == 0))
                {
                    commandLineCmds = string.Empty;
                }
                else
                {
                    commandLineCmds = " " + commandLineCmds;
                }

                string pathToTool = this.ComputePathToTool();
                if (pathToTool == null)
                {
                    throw new TaskException("ToolTask.ToolNotFound");
                }

                this.LogToolCommand(pathToTool + commandLineCmds + " " + commandResponseFile);

                this.ExitCode = this.ExecuteTool(pathToTool, commandResponseFile, commandLineCmds);
            }
            catch (ArgumentException ex1)
            {
                throw new TaskException(ex1, "General.InvalidToolSwitch");
            }
            catch (Win32Exception ex2)
            {
                throw new TaskException(ex2, "ToolTask.CouldNotStartToolExecutable");
            }
            catch (IOException ex3)
            {
                throw new TaskException(ex3, "ToolTask.CouldNotStartToolExecutable");
            }

            return;
        }

/*
 * GK previous version of InternalExecute
 *

        protected virtual bool InternalExecuteOld()
        {
            bool result = false;

            // Let the tool validate its parameters. ToolTask is responsible for logging
            // useful information about what was wrong with the parameters.
            if (!ValidateParameters())
            {
                return false;
            }

            try
            {
                if (SkipTaskExecution())
                {
                    // the task has said there's no command-line that we need to run, so
                    // return true to indicate this task completed successfully (without
                    // doing any actual work).
                    return true;
                }

                string commandLineCommands = GenerateCommandLineCommands();
                string responseFileCommands = GenerateResponseFileCommands();

                // If there are response file commands, then we need a response file later.
                if ((commandLineCommands == null) || (commandLineCommands.Length == 0))
                {
                    commandLineCommands = String.Empty;
                }
                else
                {
                    commandLineCommands = " " + commandLineCommands;
                }

                // Initialize the host object.  At this point, the task may elect
                // to not proceed.  Compiler tasks do this for purposes of up-to-date
                // checking in the IDE.  
                HostObjectInitializationStatus status1 = this.InitializeHostObject();
                if (status1 == HostObjectInitializationStatus.NoActionReturnSuccess)
                {
                    return true;
                }
                if (status1 == HostObjectInitializationStatus.NoActionReturnFailure)
                {
                    this.ExitCode = 1;
                    return this.HandleTaskExecutionErrors();
                }

                string pathToTool = ComputePathToTool();
                if (pathToTool == null)
                {
                    // An appropriate error should have been logged already.
                    return false;
                }

                // Log that we are about to invoke the specified command.  
                LogToolCommand(ToolName + commandLineCommands + " " + responseFileCommands);

                int exitCode = 0;

                if (status1 == HostObjectInitializationStatus.UseHostObjectToExecute)
                {
                    // The hosting IDE passed in a host object to this task.  Give the task
                    // a chance to call this host object to do the actual work.  
                    try
                    {
                        if (!CallHostObjectToExecute())
                            exitCode = 1;
                    }
                    catch (Exception e)
                    {
                        Log.LogErrorFromException(e);
                        return false;
                    }
                }
                else
                {
                    // No host object was provided, or at least not one that supports all of the
                    // switches/parameters we need.  So shell out to the command-line tool.
                    exitCode = ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
                    Bag["ExitCode"] = exitCode;
                }

                // Raise a comment event to notify that the process completed
                if (exitCode != 0)
                {
                    HandleTaskExecutionErrors(exitCode, true);
                }
            }
            
            catch (ArgumentException e)
            {
                Log.LogErrorWithCodeFromResources("General.InvalidToolSwitch", ToolName, e.Message);
                return;
            }
            catch (Win32Exception e)
            {
                Log.LogErrorWithCodeFromResources("ToolTask.CouldNotStartToolExecutable", e.Message);
                return;
            }
            catch (IOException e)
            {
                Log.LogErrorWithCodeFromResources("ToolTask.CouldNotStartToolExecutable", e.Message);
                return;
            }
        }
   */

    }
}